#include"Task.hpp"
#include<iostream>
#include<unistd.h>
#include<vector>
#include<string>
#include<stdlib.h>
#include<assert.h>
#include<time.h>
#include<sys/wait.h>
#include<sys/stat.h>
//用管道实现一个简单的进程池
using namespace std;

const int processnum =5;
//任务列表
vector<task_t> tasks;

//先描述
class channel
{
public:
    channel(int cmdfd,int slaverid,const string& processname)
    :_cmdfd(cmdfd),
    _slaverid(slaverid),
    _processname(processname)
    {}

public:
    int _cmdfd;          //发送任务的文件描述符
    pid_t _slaverid;     //子进程的pid
    string _processname; //子进程的名字--方便我们打印日志
};

void slaver()
{
    //read(0)//从0中读，0已经重定向到管道文件了
    while(true)
    {
        int cmdcode=0;//任务码
        int n=read(0,&cmdcode,sizeof(int));//读四个字节
                            //如果父进程不给子进程发任务码，子进程阻塞
        if(n==sizeof(int))
        {
            //执行cmdcode对应的任务列表
            cout<<"slaver say: "<<"cmdcode: "<<cmdcode<<"already read "
            <<getpid()<<" cmdcode: "
            <<cmdcode<<endl; 
            if(cmdcode>=0&&cmdcode<tasks.size())
            tasks[cmdcode]();//执行对应任务
        }
        if(n==0)
        {//写端关闭，读端读0
            break;
        }
        sleep(1);
    }
}

//参数输入输出规范
//输入型：const&
//输出型：*
//输出输出：&
//1.初始化
void InitProcessPool(vector<channel> *channels)
{
    //方法二
    //创建的时候确保每一个子进程都只有一个读端
    vector<int> oldfds;

    for(int i=0;i<processnum;i++)
    {
        int pipefd[2];  //临时空间
        int n=pipe(pipefd);
        assert(!n);//等于零,成功
        (void)n;

 
        pid_t id=fork();
        if(id==0)
        {
            //子进程关闭多余写端
            for(auto fd:oldfds) close(fd);

            //子进程
            close(pipefd[1]);
            
            dup2(pipefd[0],0);//pipefd[0]覆盖0，即0指向管道文件
            slaver();
            cout<<"process: "<<getpid()<<"quit"<<endl;         
            //slaver(pipefd[0]);

            exit(0);
        }
        //父进程
        close(pipefd[0]);   //关闭了3,下次申请管道，读仍然是3

        //添加channel字段
        string name="process-"+to_string(i);
        channels->push_back(channel(pipefd[1],id,name));
        //每次循环，都通过pipe创建了一个新管道，
        //子进程fd0控制读，父进程fdi+3控制写

        oldfds.push_back(pipefd[1]);
    }
}

void Debug(const vector<channel> &channels)
{
    for(auto &c:channels)
    {
        cout<<c._cmdfd<<" "<<c._processname<<" "<<c._slaverid<<endl;
    }

}

//控制子进程
void CtrlSlaver(const vector<channel> &channels)
{
    //也可以自己选择任务
    //写一个菜单
    //选择任务阶段从键盘读取任务码

    int which=0;
    //int cnt=5;
    //while(cnt--)
    while(true)
    {
        //1.选择任务
        int cmdcode=rand()%tasks.size();
        //2.选择进程
        //随机选择
        //int processpos=rand()%channels.size();
        //轮询选择

        cout<<"father say: "<<"cmdcode: "<<cmdcode<<"already sendto "
        <<channels[which]._slaverid<<" process name: "
        <<channels[which]._processname<<endl;
        //3.发送任务
        write(channels[which]._cmdfd,&cmdcode,sizeof(cmdcode));

        which++;
        which%=channels.size();//保证轮询

        sleep(1);
    }

}

//清理收尾
void QuitProcess(const vector<channel> &channels)
{
//     for(const auto &c:channels)
//     {
//         close(c._cmdfd);//关闭写端
//         //此处子进程继承了父进程的写端，故关闭父进程写端没用，子进程读端，没有读0，继续阻塞
//         //但是关闭最后一个写端时，该写端没有被子进程继承，写端关闭，读端读0，子进程退出，
//         //从下置上依次退出进程
//     }
//     sleep(5);
//     for(const auto &c:channels)
//     {
//         waitpid(c._slaverid,nullptr,0);
//     }
    //怎么在一个循环内完成任务呢？
    //方法一
    //倒着回收
    // int last=channels.size()-1;
    // for(int i=last;i>=0;i--)
    // {
    //     close(channels[i]._cmdfd);
    //     waitpid(channels[i]._slaverid,nullptr,0);
    // }
    //方法二
    //创建的时候确保每一个子进程都只有一个读端，见初始化部分
     for(const auto &c:channels)
    {
        close(c._cmdfd);//关闭写端
        waitpid(c._slaverid,nullptr,0);
    }
}

int main()
{
    //任务列表
    LoadTask(&tasks);
    //随机数
    srand(time(nullptr)^getpid()^1023);//种一个随机数种子
    //再组织
    vector<channel> channels;
    //1.初始化
    InitProcessPool(&channels);
    //创建了多个子进程和对应的管道

    //test
    Debug(channels);

    //2.开始控制子进程
    CtrlSlaver(channels);
    
    //3.清理收尾
    QuitProcess(channels);

    sleep(10);
    return 0;
}